package com.alex.megacj.logger;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Required;

import com.alex.megacj.handler.RequestContext;

public class RequestLogger extends TimerTask {

	private static final Logger LOGGER = Logger.getLogger(RequestLogger.class);

	private long flushInterval;

	private DateFormat dateFormat;

	private DateFormat fileDateFormat;

	private Timer timer;

	private LinkedList<LogEntry> logQueue;

	private File logDirectory;

	public void start() throws IOException {
		LOGGER.info("Starting RequestLogger");
		if (!logDirectory.isDirectory() && !logDirectory.mkdirs()) {
			throw new RuntimeException("Failed to create log directory: " + logDirectory);
		}
		timer = new Timer();
		timer.schedule(this, flushInterval, flushInterval);
		logQueue = new LinkedList<LogEntry>();
	}

	public void stop() {
		timer.cancel();
		LOGGER.info("Stopped RequestLogger");
	}

	@Override
	public synchronized void run() {
		Map<String, Writer> writers = new HashMap<String, Writer>();
		try {
			for (LogEntry entry : logQueue) {
				String fileName = getFileName(entry);
				Writer writer = writers.get(fileName);
				if (writer == null) {
					writer = new BufferedWriter(new FileWriter(new File(logDirectory, fileName), true));
					writers.put(fileName, writer);
				}
				writer.write(formatLogEntry(entry));
			}
		} catch (IOException e) {
			LOGGER.error(e.getMessage(), e);
		} finally {
			logQueue = new LinkedList<LogEntry>();
			for (Writer writer : writers.values()) {
				try {
					writer.close();
				} catch (IOException e) {
					LOGGER.error(e.getMessage(), e);
				}
			}
		}
	}

	private String formatLogEntry(LogEntry entry) {
		return entry.requestType + "\t" + dateFormat.format(entry.date) + "\t" + entry.ip + "\t" + entry.url + "\t" + entry.source + "\t" + entry.referer + "\t" + entry.userAgent + "\n";
	}

	private String getFileName(LogEntry entry) {
		return entry.mirrorHost + "_" + fileDateFormat.format(entry.date) + ".log";
	}

	public void log(RequestContext context) {
		String ip = context.request.getHeader("x-forwarded-for");
		if (ip == null) {
			ip = context.request.getRemoteAddr();
		}

		String url = StringUtils.replaceOnce(context.request.getRequestURL().toString(), ":8080", "");
		if (context.request.getQueryString() != null) {
			url += "?" + context.request.getQueryString();
		}
		
		String source = context.source;
		if (source == null) {
			source = "-";
		}
		
		String referer = context.request.getHeader("Referer");
		if (referer == null) {
			referer = "-";
		}

		String userAgent = context.request.getHeader("User-Agent");
		if (userAgent == null) {
			userAgent = "-";
		}

		synchronized (this) {
			logQueue.add(new LogEntry(context.mirrorHost, context.requestType, ip, new Date(), url, source, referer, userAgent));
		}
	}

	@Required
	public void setDateFormat(String dateFormat) {
		this.dateFormat = new SimpleDateFormat(dateFormat);
	}

	public void setFileDateFormat(String fileDateFormat) {
		this.fileDateFormat = new SimpleDateFormat(fileDateFormat);
	}

	@Required
	public void setLogDirectory(File logDirectory) {
		this.logDirectory = logDirectory;
	}

	@Required
	public void setFlushInterval(long flushInterval) {
		this.flushInterval = flushInterval;
	}
}
